﻿/*
   Copyright 2013 Dmitry Bratus

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Flower.Services;
using Flower.Services.Data;
using NUnit.Framework;
using System.Transactions;
using Flower.Testing;
using Flower.Directory.Util;

namespace Flower.Directory.Host.Default.Tests
{
    [TestFixture]
    public class ProcessesDispatcherTests
    {
        private IDirectory MakeDirectory()
        {
            var scriptEnginePool = new ScriptEnginePool(1);
            var proxyProvider = new DirectoryProxyProvider();
            var processesDispatcher = new ProcessesDispatcher();
            var dir = new MemoryDirectory.Directory(scriptEnginePool, proxyProvider, processesDispatcher);

            var dirProxy =
                new DirectoryBuilder(proxyProvider.MakeProxy(dir))
                    .Root("/Workflows")
                        .Workflow("Normal", typeof(object).AssemblyQualifiedName, null).End()
                        .Workflow("Preferred", typeof(object).AssemblyQualifiedName, new [] { "Proc1" }).End()
                    .End()
                    .RegisterProcessor("Proc1")
                    .RegisterProcessor("Proc2")
                    .RegisterProcessor("Proc3")
                    .Build();

            return dirProxy;
        }

        [Test]
        public void ProcessesDispatcherEvennes()
        {
            var dir = MakeDirectory();
            var processesDispatcher = ((IProcessesDispatcherHolder)dir).ProcessesDispatcher;
            const int processesToDispatch = 100;

            for (var i = 0; i < processesToDispatch; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    trn.Complete();
                }
            }

            var pendingCounts = new[]
            {
                dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc2/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc3/Pending", DirectoryEntryTypes.Process)
            };

            Assert.IsTrue((pendingCounts.Max() - pendingCounts.Min()) <= 1);
            Assert.IsTrue(pendingCounts.Sum() == processesToDispatch);
        }

        [Test]
        public void ProcessesDispatcherProcessStatusNotification()
        {
            var dir = MakeDirectory();
            var processesDispatcher = ((IProcessesDispatcherHolder)dir).ProcessesDispatcher;
            const int wave1ProcessCount = 30;
            const int wave2ProcessCount = 10;

            var proc1Pids = new List<string>();

            for (var i = 0; i < wave1ProcessCount; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    string pid = dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    if (procName == "Proc1")
                    {
                        proc1Pids.Add(pid);
                    }

                    trn.Complete();
                }
            }

            var pendingCounts = new[]
            {
                dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc2/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc3/Pending", DirectoryEntryTypes.Process)
            };

            Assert.IsTrue(pendingCounts.All(cnt => cnt == 10));

            foreach (string pid in proc1Pids)
            {
                dir.Move(pid, "/Processors/Proc1/Running");
            }

            var spin = 0;
            var statuses = new[] { "Finished", "Waiting", "Broken" };
            foreach (string pid in proc1Pids)
            {
                dir.Move(pid, "/Processors/Proc1/" + statuses[spin++ % statuses.Length]);
            }

            for (var i = 0; i < wave2ProcessCount; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    trn.Complete();
                }
            }

            Assert.IsTrue(dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process) == wave2ProcessCount);
        }

        [Test]
        public void ProcessesDispatcherProcessDeactivation()
        {
            var dir = MakeDirectory();
            var processesDispatcher = ((IProcessesDispatcherHolder)dir).ProcessesDispatcher;
            const int processesToDispatch = 100;

            dir.Remove("/Processors/Active/Proc1");

            for (var i = 0; i < processesToDispatch; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    trn.Complete();
                }
            }

            var pendingCounts = new[]
            {
                dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc2/Pending", DirectoryEntryTypes.Process),
                dir.CountChildren("/Processors/Proc3/Pending", DirectoryEntryTypes.Process)
            };

            Assert.IsTrue(dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process) == 0);
            Assert.IsTrue(pendingCounts.Sum() == processesToDispatch);
        }

        [Test]
        public void ProcessDispatcherPreferredProcessors()
        {
            var dir = MakeDirectory();
            var processesDispatcher = ((IProcessesDispatcherHolder)dir).ProcessesDispatcher;
            const int processesToDispatch = 10;
            
            for (var i = 0; i < processesToDispatch; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Preferred");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Preferred"
                        }
                    );

                    trn.Complete();
                }
            }

            Assert.IsTrue(dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process) == processesToDispatch);
        }

        [Test]
        public void ProcessDispatcherWorkflowsTracking()
        {
            var dir = MakeDirectory();
            var processesDispatcher = ((IProcessesDispatcherHolder)dir).ProcessesDispatcher;
            const int processesToDispatch = 10;

            var wf = (Services.Data.Workflow)dir.Get("/Workflows/Normal");
            wf.PreferredProcessors = new List<string> { "Proc1" };
            dir.Save("/Workflows", wf);

            for (var i = 0; i < processesToDispatch; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    trn.Complete();
                }
            }

            Assert.IsTrue(dir.CountChildren("/Processors/Proc1/Pending", DirectoryEntryTypes.Process) == processesToDispatch);

            wf = (Services.Data.Workflow)dir.Get("/Workflows/Normal");
            wf.PreferredProcessors = new List<string> { "Proc2" };
            dir.Save(null, wf);

            for (var i = 0; i < processesToDispatch; i++)
            {
                using (var trn = new TransactionScope())
                {
                    string procName = processesDispatcher.SelectProcessor("/Workflows/Normal");

                    Assert.IsTrue(procName != null);

                    dir.Save
                    (
                        string.Format("/Processors/{0}/Pending", procName),
                        new Process
                        {
                            Workflow = "Normal"
                        }
                    );

                    trn.Complete();
                }
            }

            Assert.IsTrue(dir.CountChildren("/Processors/Proc2/Pending", DirectoryEntryTypes.Process) == processesToDispatch);
        }
    }
}
